﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CSConsoleTest
{
    class TestParallel : BaseConsoleTest
    {

        public void printDescription()
        {
            Console.WriteLine("--- Parallel ---");
        }

        public void doTest()
        {
            testParallelForLoop();
            testParallelTypedForLoop();
            testParallelForLoopStop();

            testParallelForEachLoop();
            testParallelTypedForEachLoop();

            testCancelParallelForLoop();

            testSpeedUpParallelBodyForSmallLoop();

            try
            {
                testParallelException();
            }
            catch (AggregateException ae)
            {
                foreach (Exception e in ae.InnerExceptions)
                {
                    if (e is ArgumentOutOfRangeException)
                    {
                        ArgumentOutOfRangeException are = e as ArgumentOutOfRangeException;
                        Console.WriteLine("Exception occured : {0}", are.Message);
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
        }

        private void testParallelForLoop()
        {
            ParallelLoopResult result = Parallel.For(1, 11, (index) =>
            {
                Console.WriteLine("index : {0}, ThreadId : {1}", index, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(30);
            });
        }

        private void testParallelTypedForLoop()
        {
            long total = 0;
            ParallelLoopResult result = Parallel.For<long>(1, 11, () => 0, (index, loopState, resultValue) =>
            {
                Console.WriteLine("index : {0}, ThreadId : {1}", index, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(30);
                resultValue += index;
                return resultValue;
            },
            (x) =>
            {
                Interlocked.Add(ref total, x);
                Console.WriteLine("fin : {0}, ThreadId : {1}", x, Thread.CurrentThread.ManagedThreadId);
            });

            Console.WriteLine("Total : {0}", total);
        }

        private void testParallelForLoopStop()
        {
            ParallelLoopResult result = Parallel.For(1, 11, (index, loopState) =>
            {
                Console.WriteLine("index : {0}, ThreadId : {1}", index, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(30);

                if (index == 5)
                {
                    loopState.Stop();
                }
            });
        }

        private void testParallelForEachLoop()
        {
            int[] intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

            ParallelLoopResult result = Parallel.ForEach(intArray, (index) =>
            {
                Console.WriteLine("index : {0}, ThreadId : {1}", index, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(30);
            });
        }

        private void testParallelTypedForEachLoop()
        {
            int[] intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
            long total = 0;

            ParallelLoopResult result = Parallel.ForEach<int, long>(intArray, () => 0, (index, loopState, subTotal) =>
            {
                Console.WriteLine("index : {0}, ThreadId : {1}", index, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(30);
                subTotal += index;
                return subTotal;
            },
            subTotalResult =>
            {
                Interlocked.Add(ref total, subTotalResult);
            });

            Console.WriteLine("Total : {0}", total);
        }

        private void testSpeedUpParallelBodyForSmallLoop()
        {
            var calcSrc = Enumerable.Range(1, 10).ToArray();
            var partitionedSrc = Partitioner.Create(0, calcSrc.Length);

            double[] results = new double[calcSrc.Length];

            Parallel.ForEach(partitionedSrc, (range, loopState) =>
            {
                for (int index = range.Item1; index < range.Item2; index++)
                {
                    results[index] = calcSrc[index] * Math.PI;
                }
            });

            foreach (double result in results)
            {
                Console.WriteLine("{0}", result);
            }
        }

        private void testCancelParallelForLoop()
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            ParallelOptions parallelOptions = new ParallelOptions();
            parallelOptions.CancellationToken = cts.Token;
            parallelOptions.MaxDegreeOfParallelism = System.Environment.ProcessorCount;
            Console.WriteLine("Cancelation test. processor count : {0}. press 'c' to cancel", System.Environment.ProcessorCount);

            Task.Factory.StartNew(() => {
                if (Console.ReadKey().KeyChar == 'c')
                    cts.Cancel();
            });

            try
            {
                ParallelLoopResult result = Parallel.For(1, 11, parallelOptions, (index) =>
                {
                    Console.WriteLine("index : {0}, ThreadId : {1}", index, Thread.CurrentThread.ManagedThreadId);
                    Thread.Sleep(3000);
                });
            }
            catch (OperationCanceledException opCanceledException)
            {
                Console.WriteLine("Exception occured : {0}", opCanceledException.Message);
            }
        }

        private void testParallelException()
        {

            var exceptions = new ConcurrentQueue<Exception>();

            Parallel.For(1, 11, (index) =>
            {
                try
                {
                    if (index == 6 || index == 8)
                    {
                        throw new ArgumentOutOfRangeException("index", index.ToString(), "this test method doesn't want to get number 8 or 6.");
                    }
                    else
                    {
                        Console.WriteLine("Not exception value : {0}", index);
                    }
                }
                catch (ArgumentOutOfRangeException ae)
                {
                    exceptions.Enqueue(ae);
                }
            });

            if (exceptions.Count > 0)
            {
                throw new AggregateException(exceptions);
            }
        }
    }
}
